#include "stdafx.h"

namespace Melting
{
namespace Profiling
{
	struct Event
	{
		LARGE_INTEGER			mTime; 
		Parallel::ThreadID		mThreadID;
		const char*				mstrName;
	}; 


	LARGE_INTEGER						gSystemFrequency;	///< Clock frequency
	LARGE_INTEGER						gSystemStart;		///< Clock frequency

	Parallel::TLS< StatsBuffer * >		tlsStats;	///< Per Thread StatsBuffer
	StatsBuffer							gStats;		///< Global StatsBuffer
	Parallel::Mutex						gMutex;		///< Mutex to protect the global StatsBuffer
	UINT32								gMaxStats;	///< Max Stats per Thread (!)

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void Initialize( UINT32 _iMaxStats, UINT32 _iMaxThreads )
	{	
		gMaxStats = _iMaxStats;	// no lock since it must be called on init
		gStats.reserve( gMaxStats * _iMaxThreads );

		QueryPerformanceFrequency( & gSystemFrequency );
		QueryPerformanceCounter( & gSystemStart );

		//MEASURE_START();
		//printf("Init\n");
		//MEASURE_STOP_LOG("yop", 0 );

		// Initialize the TLS for the main thread
		InitializeTLS();
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void Release( void )
	{
		// Release the TLS for the main thread
		ReleaseTLS();
	}
	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void InitializeTLS( void )
	{	
		//------------------
		// precondition
		MB_ASSERT( nullptr == tlsStats.Get(), "TLS already created?\n" );
		//-------------------
		//
		//MEASURE_START();
		//printf("InitTLS\n");
		//MEASURE_STOP_LOG("yop", 0 );


		StatsBuffer* pStats = new StatsBuffer();
		pStats->reserve( gMaxStats );
		tlsStats.Set( pStats );
 	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void ReleaseTLS( void )
	{		
		//------------------
		// precondition
		MB_ASSERT( nullptr != tlsStats.Get(), "TLS not created?\n" );
		//-------------------

		StatsBuffer* pStats = tlsStats.Get();
		delete pStats;
		tlsStats.Set( nullptr );
	}
 
	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	StatsID BeginRecordTime( const char* _strName )
	{	
		//------------------
		// precondition
		MB_ASSERT( nullptr != tlsStats.Get(), "TLS not created?\n" );
		//-------------------

		StatsBuffer* pStats = tlsStats.Get(); 

		TimeStats stats;
		
		StatsID iStatID = pStats->size();
		// FIXME : this will end up eating your whole RAM because we only push back and never recycle unless we call MergeTLSStats
		pStats->push_back( stats ); // ...

		TimeStats& rStats = (*pStats)[ iStatID ];

		rStats.mThreadID	= Parallel::Thread::GetCurrentThreadID();
		rStats.mstrName		= _strName;
 
		QueryPerformanceCounter( & rStats.mStartTime );

		return iStatID;
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void EndRecordTime( StatsID _iStatID )
	{		
		//------------------
		// precondition
		MB_ASSERT( nullptr != tlsStats.Get(), "TLS not created?\n" );
		//-------------------

		StatsBuffer* pStats = tlsStats.Get();

		MB_ASSERT( nullptr != pStats, "TLS not properly created\n" );

		// get it from _StatID
		TimeStats& rStats = (*pStats)[ _iStatID ];
		QueryPerformanceCounter( & rStats.mEndTime );
	}
 
	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void MergeTLSStats( void )
	{
		//------------------
		// precondition
		MB_ASSERT( nullptr != tlsStats.Get(), "TLS not created?\n" );
		//-------------------

		StatsBuffer* pStats = tlsStats.Get(); 
  
		gMutex.Lock();
		{
			gStats.insert( gStats.end(), pStats->begin(), pStats->end() );
		}
		gMutex.Unlock();

		pStats->clear();
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	bool CompareStartTime( const TimeStats& _rStatsA, const TimeStats& _rStatsB )
	{
		return _rStatsA.mStartTime.QuadPart < _rStatsB.mStartTime.QuadPart;
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	bool CompareThreadID( const TimeStats& _rStatsA, const TimeStats& _rStatsB )
	{
		return _rStatsA.mThreadID < _rStatsB.mThreadID;
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void SortStatsByStartTime( void )
	{
		gMutex.Lock();
		{
			std::sort( gStats.begin(), gStats.end(), CompareStartTime );
		}
		gMutex.Unlock();
	}
	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void SortStatsByThread( void )
	{
		gMutex.Lock();
		{
			std::sort( gStats.begin(), gStats.end(), CompareThreadID );
		}
		gMutex.Unlock();
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void ClearStats( void )
	{
		gMutex.Lock();
		{
			gStats.clear();
		}
		gMutex.Unlock();
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	LARGE_INTEGER GetSystemStartTime( void )
	{
		return gSystemStart;
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	LARGE_INTEGER GetSystemFrequency( void )
	{
		return gSystemFrequency;
	}

	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	const StatsBuffer& GetStats( void )
	{
		return gStats;
	}
 
	//------------------------------------------------------------------------
	//------------------------------------------------------------------------
	void PrintStats( void )
	{
		gMutex.Lock();
		MSG( "=============================\nProfiling stats\n\n" );
		MSG( "Nb entries : %d\n", gStats.size() );

		for( unsigned int i = 0; i < gStats.size(); i++ )
		{
			const TimeStats& rStats = gStats[ i ];
			
			//MSG( "Start : %f;", (double) rStats.mStartTime.QuadPart / rStats.mFrequency.QuadPart );
			//MSG( "End : %f;", (double) rStats.mEndTime.QuadPart / rStats.mFrequency.QuadPart ); 
			MSG( "Start : %f;", (double) (rStats.mStartTime.QuadPart - gSystemStart.QuadPart) / gSystemFrequency.QuadPart );
			MSG( "End : %f;", (double) (rStats.mEndTime.QuadPart - gSystemStart.QuadPart) / gSystemFrequency.QuadPart ); 
			//MSG( "Start : %d;", rStats.mStartTime.QuadPart );
			//MSG( "End : %d;", rStats.mEndTime.QuadPart ); 
			MSG( "Thread : %d;", rStats.mThreadID ); 
			MSG( "Name : '%s'\n" , rStats.mstrName );
		}
		gMutex.Unlock();
	}
}


}// namespace Melting